#pragma once

#include <iostream>
#include <string>
#include "Socket.hpp"
#include "InetAddr.hpp"
#include <memory>
#include <algorithm>

#include <sys/select.h>
using namespace LogModule;
using std::endl, std::cout, std::string, std::sort;
using std::sort, std::fstream;

using namespace socketModule;
const int gdefaultfd = -1;

#define NUM sizeof(fd_set) * 8
class selectServer
{
public:
    selectServer(u_int16_t port) : _port(port),
                                   _listen_socket(std::make_unique<tcpSocket>())
    {
    }
    ~selectServer()
    {
    }

    void init()
    {
        _listen_socket->bulidTcpSocketMethod(_port);
        for (int i = 0; i < NUM; i++)
        {
            _fd_array[i] = gdefaultfd;
            // zdl:: 添加 istensockfd 的细节
            _fd_array[0] = _listen_socket->fd();
        }
    }
    void loop()
    {
        _isrunning = true;
        while (_isrunning)
        {

            fd_set rfds; // 读文件描述集
            InetAddr client;
            // 根据listenSock, 检测他现在有没有新连接
            // accept 会阻塞么？
            // 从 listenSock 获取 新连接
            // 我们这样的 IO input -> listensock 只会关注读事件
            // int newSock = _listen_socket->accepter(&client);

            // 所以现在我们需要把listensocket 添加到 select内部

            // * 1. 清空 rfd
            FD_ZERO(&rfds);
            // * 2.讲 listenSockfd 添加到 fd_set 中
            FD_SET(_listen_socket->fd(), &rfds);
            struct timeval timeout = {10, 0};
            int maxfd = gdefaultfd;
            // * 现在就要判断这个fd 是否合法
            for (int i = 0; i < NUM; i++)
            {
                if (_fd_array[i] == gdefaultfd)
                    continue;

                // ! 在这里就可以直接更新出最大的 合法的 fd
                if (maxfd < _fd_array[i])
                {
                    maxfd = _fd_array[i];
                    // 更新完成
                }
                // 走到下面就是合法的 fd。
                // 现在才上市完成了最简单的操作
                FD_SET(_fd_array[i], &rfds);
                // zdl:: 现在才是讲这个文件描述符添加到了这个的文件及 set_当中！！！
            }

            // 将合法的文件描述符添加到 rfds中

            // * 3. 我可能不能使用accept 来进行阻塞调用。而应该使用select进行就绪事件的监测
            // /此时就是用户告诉内核，你需要帮我关心 &rdfs
            int n = select(_listen_socket->fd() + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                cout << "time out..." << endl;
                break;
            case -1:
                perror("select");
                break;
            default:
                // 有事件就绪
                cout << "有事件就绪" << " timeout: " << timeout.tv_sec << ":" << timeout.tv_usec << endl;

                handlerEvents(rfds);
                break;
            }
        }
        _isrunning = false;
    }

private:
    void acceptor()
    {
        InetAddr client;
        // _listenSock 就绪，获取新链接
        int newfd = _listen_socket->accepter(&client); // 会不会被阻塞呢
        if (newfd < 0)
            return;
        else
        {
            std::cout << "获取到了一个新链接: " << newfd << " client info: " << client.Addr() << std::endl;
            // recv() ??
            // zdl:: 现在的问题就是你怎么把这个newfd 托管给select
            // zdl:: 让select 来关心上面的事件
            // zdl: 将这个newfd 添加到辅助数组就可完成这样的任务
            int pos = -1;
            for (int j = 0; j < NUM; j++)
            {
                if (_fd_array[j] == gdefaultfd)
                {
                    pos = j;
                    break;
                }
            }
            if (pos == -1)
            {
                LOG(LogLevel::ERROR) << "服务器已经满了。。。";
                close(newfd);
                return;
            }
            if (pos < NUM)
            {
                _fd_array[pos] = newfd;
            }
        }
    }

    void recver(int &who)
    {
        // 合法的，普通的，就绪的 fd
        // zdl:: 虽然这里我们已经读到了这个么多的buffer,
        // 但是还是存在在很多的问题， 还是需要我们对代码进行改进
        char buffer[1024];
        ssize_t n = ::recv(_fd_array[who], buffer, sizeof(buffer) - 1, 0); // 此时的问题就是在这样的情况下，还会不会阻塞
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            // 现在除了打印我们还需将它回显
            std::string message = std::string("echo# ") + buffer;
            send(_fd_array[who], message.c_str(), message.size(), 0);
        }
        else if (n == 0)
        {
            LOG(LogLevel::DEBUG) << "客户端退出， sockfd: " << _fd_array[who];
            close(_fd_array[who]);
            _fd_array[who] = gdefaultfd;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "客户端读取出错， sockfd:" << _fd_array[who] ;
            close(_fd_array[who]);
            _fd_array[who] = gdefaultfd;
        }
    }
    void handlerEvents(fd_set &rfds)
    {
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_array[i] == gdefaultfd)
            {
                continue;
            }
            // 文件描述符首先必须是合法的
            // 判断 listensock中，是否在 rfds中
            if (_fd_array[i] == _listen_socket->fd())
            {
                if (FD_ISSET(_listen_socket->fd(), &rfds))
                {
                    acceptor();
                }
            }
            else
            {
                if (FD_ISSET(_fd_array[i], &rfds))
                {
                    recver(i); // * 进行 IO的处理
                }
            }
        }
    }

    uint16_t _port;
    std::unique_ptr<Socket> _listen_socket;
    bool _isrunning;
    int _fd_array[NUM];
};

